﻿


''' <summary>
''' Base64 url and filename codec according to RFC4648.
''' </summary>
Public Module Base64UrlCodec

#Region "Private Fields"
    'Base64Url Alphabet Array:
    Private _Base64UrlAlphabet As Char() = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".ToCharArray
    'Encodes 3 Bytes to 4 Base64 characters.
    Private Function _Encode3BytesTo4Chars(ByVal b0 As Byte, ByVal b1 As Byte, ByVal b2 As Byte) As String
        Return (_Base64UrlAlphabet((b0 >> 2)) & _Base64UrlAlphabet((((b0 << 6) >> 2) Or (b1 >> 4))) & _Base64UrlAlphabet((((b1 << 4) >> 2) Or (b2 >> 6))) & _Base64UrlAlphabet(((b2 << 2) >> 2)))
    End Function
    'Encodes 2 Bytes to 4 Base64 characters,padded with 1 "="c
    Private Function _Encode2BytesTo4Chars(ByVal b0 As Byte, ByVal b1 As Byte) As String
        Return (_Base64UrlAlphabet((b0 >> 2)) & _Base64UrlAlphabet((((b0 << 6) >> 2) Or (b1 >> 4))) & _Base64UrlAlphabet(((b1 << 4) >> 2)) & "="c)
    End Function
    'Encodes 1 Bytes to 4 Base64 characters,padded with 2 "="c
    Private Function _Encode1BytesTo4Chars(ByVal b0 As Byte) As String
        Return (_Base64UrlAlphabet((b0 >> 2)) & _Base64UrlAlphabet(((b0 << 6) >> 2)) & "="c & "="c)
    End Function
    'Decodes 4 Base64 Bytes to 3 Bytes.
    Private Function _Decode4B64Bytes(ByVal b0 As Byte, ByVal b1 As Byte, ByVal b2 As Byte, ByVal b3 As Byte) As Byte()
        Return {((b0 << 2) Or (b1 >> 4)), ((b1 << 4) Or (b2 >> 2)), ((b2 << 6) Or b3)}
    End Function
    'Decode 3 Base64 Bytes to 2 Byte.
    Private Function _Decode3B64Bytes(ByVal b0 As Byte, ByVal b1 As Byte, ByVal b2 As Byte) As Byte()
        Return {((b0 << 2) Or (b1 >> 4)), ((b1 << 4) Or (b2 >> 2))}
    End Function
    'Decode 2 Base64 Bytes to 1 Byte.
    Private Function _Decode2B64Bytes(ByVal b0 As Byte, ByVal b1 As Byte) As Byte
        Return ((b0 << 2) Or (b1 >> 4))
    End Function
#End Region

#Region "Public Method"
    'IsValid
    ''' <summary>
    ''' Checks if a given Base64Url String is valid or not.
    ''' </summary>
    ''' <param name="b64UrlString">The Base64Url String to validate.</param>
    Public Function IsValid(ByVal b64UrlString As String) As Boolean
        If b64UrlString.IndexOf("=") < b64UrlString.Length - 2 Then
            Return False
        Else
            For Each c As Char In b64UrlString.ToCharArray
                If _Base64UrlAlphabet.Contains(c) = False Then
                    Return False
                End If
            Next
            Return True
        End If
    End Function
    'Encode
    ''' <summary>
    ''' Gets the Base64Url string representation of the given bytes array.
    ''' Return Nothing, if (sourceBytes) is Nothing or empty.
    ''' </summary>
    ''' <param name="sourceBytes">The bytes array that will be converted to Base64Url string.</param>
    Public Function Encode(ByVal sourceBytes As Byte()) As String
        If sourceBytes IsNot Nothing AndAlso sourceBytes.Count > 0 Then
            Dim B64S As String = ""
            Dim count As Integer = sourceBytes.Count
            Dim remains As Integer = (count Mod 3)
            Dim groups As Integer = CInt(((count - remains) / 3))
            For g As Integer = 0 To groups - 1
                B64S &= _Encode3BytesTo4Chars(sourceBytes((3 * g) + 0), sourceBytes((3 * g) + 1), sourceBytes((3 * g) + 2))
            Next
            If remains = 2 Then
                B64S &= _Encode2BytesTo4Chars(sourceBytes((count - 2)), sourceBytes((count - 1)))
            ElseIf remains = 1 Then
                B64S &= _Encode1BytesTo4Chars(sourceBytes((count - 1)))
            End If
            Return B64S
        Else
            Return Nothing
        End If
    End Function
    'Encode
    ''' <summary>
    ''' Gets the Base64Url string representation of the given source string according to its source text encoding.
    ''' </summary>
    ''' <param name="sourceString">The source string that will be converted to Base64Url string.</param>
    ''' <param name="sourceEncoding">The text encoding of the source string.</param>
    Public Function Encode(ByVal sourceString As String, ByVal sourceEncoding As Text.Encoding) As String
        Return Base64UrlCodec.Encode(sourceEncoding.GetBytes(sourceString))
    End Function
    'Decode
    ''' <summary>
    ''' Gets the bytes array representing the given Base64 string.
    ''' </summary>
    ''' <param name="b64UrlString">The Base64Url string to be decoded.</param>
    Public Function Decode(ByVal b64UrlString As String) As Byte()
        Dim tb64string As String = b64UrlString.TrimEnd("="c)
        Dim b64bytes As Byte() = (From c As Char In tb64string.ToCharArray Select CByte(Array.IndexOf(_Base64UrlAlphabet, c))).ToArray
        Dim count As Integer = b64bytes.Count
        Dim remains As Integer = (count Mod 4)
        Dim groups As Integer = CInt((count - remains) / 4)
        Dim result As New List(Of Byte)
        For g As Integer = 0 To groups - 1
            result.AddRange(_Decode4B64Bytes(b64bytes(4 * g + 0), b64bytes(4 * g + 1), b64bytes(4 * g + 2), b64bytes(4 * g + 3)))
        Next
        If remains = 2 Then
            result.Add(_Decode2B64Bytes(b64bytes((count - 2)), b64bytes((count - 1))))
        ElseIf remains = 3 Then
            result.AddRange(_Decode3B64Bytes(b64bytes((count - 3)), b64bytes((count - 2)), b64bytes((count - 1))))
        End If
        Return result.ToArray
    End Function
    'Decode
    ''' <summary>
    ''' Gets string array representing the given Base64Url string.
    ''' </summary>
    ''' <param name="b64UrlString">The Base64Url string to be decoded.</param>
    ''' <param name="resultEncoding">The text encoding for the resulting string.</param>
    Public Function Decode(ByVal b64UrlString As String, resultEncoding As Text.Encoding) As String
        Return resultEncoding.GetString(Base64UrlCodec.Decode(b64UrlString))
    End Function
#End Region

End Module